0%

季EPS增加股票篩選 | 用程式打造選股策略(9)

前言

今天應該也算是偷懶的一天,畢竟算是相對簡單的東西…
眼看季報將公布,因此做這一篇來找出和去年同季相比EPS增加的個股
雖然今天才13號,季報還沒有完全公布完,但還是能針對已經公布的來做一些篩選的動作…

資料可參考 用 C# .NET Core 爬取每季財報 這篇爬下來的資料

程式部分

後端: .NET Core Web API
前端: Vue.js

後端

建立回傳的Model:

1
2
3
4
5
6
7
8
9
10
public class SeasonRevenueIncrease
{
public string stock_id { get; set; }
public string stock_name { get; set; }
public decimal eps { get; set; }
public decimal last_year_eps { get; set; }
public decimal increase_eps_rate { get; set; }
public long gross_profit { get; set; } //營業毛利(毛損)淨額
public long operating_income { get; set; } //營業收入
}

SQL & Repository

設計成可以由使用者選擇年/季,方便比較各季的狀況

另外這裡SQL應該是相對比較複雜的,用了 PIVOT 來將 ROW 轉成 COLUMN
參考: Microsoft - 使用 PIVOT 和 UNPIVOT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class RevenueRepository
{
private readonly SqlConnection _conn;
private readonly ILogger<RevenueRepository> _logger;
public RevenueRepository(ILogger<RevenueRepository> logger, SqlConnection conn)
{
_logger = logger;
_conn = conn;
}


public IEnumerable<SeasonRevenueIncrease> GetSeasonRevenueIncrease(int year, int season)
{
return _conn.Query<SeasonRevenueIncrease>(
@"select
ThisSeason.*,
stockinfo.stock_name,
LastYearSeason.eps last_year_eps,
(Convert(decimal(10,2),ThisSeason.eps) - Convert(decimal(10,2),LastYearSeason.eps))/Convert(decimal(10,2),ThisSeason.eps)*100 increase_eps_rate
from
(
SELECT
stock_id,
[基本每股盈餘(元)] eps,
Convert(bigint,replace([營業毛利(毛損)淨額],',','')) gross_profit,
Convert(bigint,replace([營業收入],',','')) operating_income
FROM
(SELECT stock_id, item, value FROM SeasonReport
WHERE year =@year and season =@season and type = 1 ) AS SourceTable
PIVOT
(
max(value)
FOR item IN ( [公司名稱], [基本每股盈餘(元)],[營業毛利(毛損)淨額], [營業收入])
) AS temp
) ThisSeason,
(
SELECT
stock_id,
[基本每股盈餘(元)] eps,
Convert(bigint,replace([營業毛利(毛損)淨額],',','')) gross_profit,
Convert(bigint,replace([營業收入],',','')) operating_income
FROM
(SELECT stock_id, item, value FROM SeasonReport
WHERE year =@last_year and season =@season and type = 1 ) AS SourceTable
PIVOT
(
max(value)
FOR item IN ( [公司名稱], [基本每股盈餘(元)],[營業毛利(毛損)淨額], [營業收入])
) AS temp
) LastYearSeason,
(select distinct stock_id, stock_name from TaiwanStockInfo) stockinfo
where
ThisSeason.stock_id = LastYearSeason.stock_id and
ThisSeason.stock_id = stockinfo.stock_id and
ThisSeason.eps > LastYearSeason.eps and
Convert(decimal,ThisSeason.eps) > 0
order by increase_eps_rate desc",
new {
year,
season,
last_year = year - 1
}
);
}
}

Web Api

很簡單,就只是丟Json到前端去處理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[ApiController]
[Route("api/[controller]/[action]")]
public class RevenueController
{
private readonly ILogger<RevenueController> _logger;
private RevenueRepository _revenueRepository;

public RevenueController(ILogger<RevenueController> logger, RevenueRepository revenueRepository)
{
_logger = logger;
_revenueRepository = revenueRepository;
}

[HttpGet]
public IActionResult GetSeasonRevenueIncrease(int year, int season)
{
return new JsonResult(_revenueRepository.GetSeasonRevenueIncrease(year, season));
}

}

前端

還月營收一樣,直接用一個Table呈現
這裡直接貼出整個 Component 吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<template>
<div class="container">
<h2>季EPS增加排行</h2>
<div class="select-items">
<select v-model="query.year" @change="getSeasonRevenueIncrease()">
<option v-for="year in years" :value="year" :key="year">{{year}}</option>
</select>
<span>年</span>
<select v-model="query.season" @change="getSeasonRevenueIncrease()">
<option v-for="season in seasons" :value="season" :key="season">{{season}}</option>
</select>
<span>季</span>
</div>
<div v-if="seasonRevenueIncreaseList === null">Loading...</div>
<table v-else class="season-revenue-table">
<caption>單位: 新台幣仟元</caption>
<thead>
<tr>
<th>股票代號</th>
<th>股票名稱</th>
<th>本季EPS</th>
<th>去年同季EPS</th>
<th>EPS季增率(%)</th>
<th>營業毛利</th>
<th>營業收入</th>
</tr>
</thead>
<tbody>
<tr v-if="seasonRevenueIncreaseList.length === 0">
<td colspan="7">查無資料</td>
</tr>
<tr v-else v-for="seasonRevenueIncrease in seasonRevenueIncreaseList" :key="seasonRevenueIncrease.stock_id">
<td>{{seasonRevenueIncrease.stock_id}}</td>
<td>{{seasonRevenueIncrease.stock_name}}</td>
<td>{{seasonRevenueIncrease.eps.toFixed(2)}}</td>
<td>{{seasonRevenueIncrease.last_year_eps.toFixed(2)}}</td>
<td>{{seasonRevenueIncrease.increase_eps_rate.toFixed(2)}}%</td>
<td>{{seasonRevenueIncrease.gross_profit}}</td>
<td>{{seasonRevenueIncrease.operating_income}}</td>
</tr>
</tbody>
</table>
</div>
</template>

<script>
export default {
name: 'SeasonRevenueIncrease',
data () {
return {
seasonRevenueIncreaseList: null,
years: Array(10).fill(new Date().getFullYear() - 1911).map((item,index) => item - index),
seasons: Array(4).fill().map((item, index) => index + 1),
query: {
year: new Date().getFullYear() - 1911,
season: 1
}
}
},
async created () {
await this.getSeasonRevenueIncrease();
},
mounted () {

},
methods: {
async getSeasonRevenueIncrease() {
let response = await this.axios.get(`/api/Revenue/GetSeasonRevenueIncrease?year=${this.query.year}&season=${this.query.season}`)
this.seasonRevenueIncreaseList = response.data
}
}
}
</script>
<style lang="stylus" scoped>
h2
margin 20px 0px

.select-items
margin 10px

select
margin 0px 5px

.season-revenue-table
margin 0 auto

caption
text-align: right;
padding: 5px 2px;
font-size:14px
tr
th, td
border 1px solid #ccc
padding 7px 9px
</style>

結果

由於季報還沒有完全公布完,因此這裡是針對目前(5/13)已經公布的資料做的篩選:

心得

目前篩選出的檔數不多,其實可以有季增的表現應該算是蠻厲害的了,畢竟今年武漢肺炎的爆發影響算是不小..
然而現在各國陸續解除封城的狀態,也有一些二次爆發的新聞出來,也許夏天還是沒辦法消滅病毒,投資還是需要謹慎..

↓↓↓ 如果喜歡我的文章,可以幫我按個Like! ↓↓↓
>> 或者,請我喝杯咖啡,這樣我會更有動力唷! <<<
街口支付

街口支付

街口帳號: 901061546

歡迎關注我的其它發布渠道